﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IInject.Registration;
using IInject.Resolution;

namespace IInject.Diagnostics
{
    public static class ContainerDiagnosticExtensions
    {
		/// <summary>
		/// NOTE: This is for debugging purposes and should not used in a production environment!
		/// Enumerates through all the registered types and returns a
		/// string log containing information about what is registered.
		/// </summary>
		/// <param name="container">The <see cref="IContainer"/> to run a self on.</param>
		/// <returns>Returns a log of all the registered types in the container.</returns>
		public static string RunSelfDiagnostics(this IContainer container)
		{
			using (var writer = new StringWriter())
			{
				container.RunSelfDiagnostics(writer);
				return writer.ToString();
			}
		}

		/// <summary>
		/// NOTE: This is for debugging purposes and should not used in a production environment!
		/// Enumerates through all the registered types and writes a string log
		/// to the provided writer containing information about what is registered.
		/// </summary>
		/// <param name="container">The <see cref="IContainer"/> to run a self on.</param>
		/// <param name="writer">The writer the log will be written to.</param>
		public static void RunSelfDiagnostics(this IContainer container, TextWriter writer)
		{
			var defaultRegistry = container.Registry.Get<DefaultContextRegistry>();
			writer.WriteLine("--------------IInject.Container Self Diagnostics--------------");
			writer.WriteLine();
			writer.WriteLine("{0}", defaultRegistry.ToString());
			writer.WriteLine();

			foreach (var context in defaultRegistry)
			{
				WriteRegistryContext(writer, context);
			}

			var keyedRegistry = container.Registry.Get<KeyedContextRegistry>();

			if (keyedRegistry.Count > 0)
			{
				writer.WriteLine();
				writer.WriteLine();
				writer.WriteLine();
				writer.WriteLine("{0}", keyedRegistry.ToString());

				foreach (var list in keyedRegistry)
				{
					foreach (var context in list)
					{
						WriteRegistryContext(writer, context.Item2);
					}
				}
			}
			else
			{
				writer.WriteLine();
				writer.WriteLine("{0}", keyedRegistry.ToString());
			}

			var enumerableRegistry = container.Registry.Get<EnumerableContextRegistry>();

			if (enumerableRegistry.Count > 0)
			{
				writer.WriteLine();
				writer.WriteLine();
				writer.WriteLine();
				writer.WriteLine("{0}", enumerableRegistry.ToString());

				foreach (var list in enumerableRegistry)
				{
					foreach (var context in list)
					{
						WriteRegistryContext(writer, context);
					}
				}
			}
			else
			{
				writer.WriteLine();
				writer.WriteLine("{0}", enumerableRegistry.ToString());
			}

			var keyedEnumerableRegistry = container.Registry.Get<KeyedEnumerableContextRegistry>();

			if (keyedEnumerableRegistry.Count > 0)
			{
				writer.WriteLine();
				writer.WriteLine();
				writer.WriteLine();
				writer.WriteLine("{0}", keyedEnumerableRegistry.ToString());

				foreach (var list in keyedEnumerableRegistry)
				{
					foreach (var list1 in list)
					{
						foreach (var context in list1.Item2)
						{
							WriteRegistryContext(writer, context);
						}
					}
				}
			}
			else
			{
				writer.WriteLine();
				writer.WriteLine("{0}", keyedEnumerableRegistry.ToString());
			}
		}

		/// <summary>
		/// NOTE: This is for debugging purposes and should not used in a production environment!
		/// Tries to resolve every type registered and returns a log of all the failed resolves.
		/// </summary>
		/// <param name="container">The <see cref="IContainer"/> to run a self on.</param>
		/// <returns>Returns a log of all the registered types in the container.</returns>
		public static string RunResolveAllSelfDiagnostics(this IContainer container)
		{
			using (var writer = new StringWriter())
			{
				container.RunResolveAllSelfDiagnostics(writer);
				return writer.ToString();
			}
		}

		/// <summary>
		/// NOTE: This is for debugging purposes and should not used in a production environment!
		/// Tries to resolve every type registered and returns a log of all the failed resolves.
		/// </summary>
		/// <param name="container">The <see cref="IContainer"/> to run a self on.</param>
		/// <param name="writer">The writer the log will be written to.</param>
		public static void RunResolveAllSelfDiagnostics(this IContainer container, TextWriter writer)
		{
			writer.WriteLine("--------------IInject.Container ResolveAll Self Diagnostics--------------");
			var resolver = container.Resolver;

			foreach (var context in container.Registry.Get<DefaultContextRegistry>())
			{
				ResolveRegistryContextAndWriteIfFailed(writer, resolver, context);
			}

			foreach (var list in container.Registry.Get<KeyedContextRegistry>())
			{
				foreach (var context in list)
				{
					ResolveRegistryContextAndWriteIfFailed(writer, resolver, context.Item2);
				}
			}

			foreach (var list in container.Registry.Get<EnumerableContextRegistry>())
			{
				foreach (var context in list)
				{
					ResolveRegistryContextAndWriteIfFailed(writer, resolver, context);
				}
			}

			foreach (var list in container.Registry.Get<KeyedEnumerableContextRegistry>())
			{
				foreach (var list1 in list)
				{
					foreach (var context in list1.Item2)
					{
						ResolveRegistryContextAndWriteIfFailed(writer, resolver, context);
					}
				}
			}
		}

		private static void WriteRegistryContext(TextWriter writer, RegistryContext context)
		{
			writer.WriteLine();
			writer.WriteLine("Key: {0}", context.Key ?? "null");
			writer.WriteLine("Scope: {0}", context.Scope.ToString());
			writer.WriteLine("FromTypeName: {0}", context.FromType != null ? context.FromType.FullName : "null");
			writer.WriteLine("ToTypeName: {0}", context.ToType != null ? context.ToType.FullName : "null");
			writer.WriteLine("Instance: {0}", context.Instance != null ? context.Instance.GetType().FullName : "null");
			writer.WriteLine("PropertiesToInject: {0}", context.Properties.Count);

			if (context.Properties.Count > 0)
			{
				writer.WriteLine();
				var properties = context.Properties;

				for (int i = 0; i < properties.Count; i++)
				{
					writer.WriteLine("Properties[{0}]: Name: '{1}' Type: {2}", i, properties[i].Property.Name, properties[i].Property.PropertyType.FullName);
				}

				writer.WriteLine();
			}
			writer.WriteLine();
		}

		private static void ResolveRegistryContextAndWriteIfFailed(TextWriter writer, ITypeDependencyResolver resolver, RegistryContext context)
		{
			try
			{
				resolver.Build(context);
			}
			catch (Exception e)
			{
				writer.WriteLine(e.Message);
			}
		}
    }
}
